#pragma once

#include <thread>
#include <fstream>

#include <google/protobuf/message.h>
#include <google/protobuf/dynamic_message.h>
#include <google/protobuf/util/json_util.h>

#include <grpcpp/grpcpp.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>

#include "glog/logging.h"

// #include "grpc_utils/proto_reflection_descriptor_database.h"
// #include "grpc_utils/reflection.grpc.pb.h"

template <class... ServerTypes>
class GrpcUtilsServer : public ServerTypes...
{
public:
  GrpcUtilsServer() {}
  virtual ~GrpcUtilsServer() { stop(); }

  virtual int start(std::string address)
  {
    if (server_)
    {
      LOG(ERROR) << "start already: " << address;
      return -1;
    }
    server_address_ = address;
    builder_ = std::make_shared<grpc::ServerBuilder>();
    builder_->AddListeningPort(server_address_,
                               grpc::InsecureServerCredentials());
    (builder_->RegisterService(dynamic_cast<ServerTypes *>(this)), ...);
    builder_->SetMaxReceiveMessageSize(100 * 1024 * 1024);
    grpc::reflection::InitProtoReflectionServerBuilderPlugin();
    server_ = builder_->BuildAndStart();
    if (!server_)
    {
      builder_.reset();
      LOG(ERROR) << "invalid server address: " << server_address_;
      return -1;
    }
    LOG(INFO) << "connect: " << address;

    return 1;
  }

  virtual void stop()
  {
    if (server_)
    {
      LOG(WARNING) << "server_->Shutdown()";
      server_->Shutdown();
      server_->Wait();
      server_.reset();
      builder_.reset();
      LOG(WARNING) << "stop() finish";
    }
    else
    {
      LOG(WARNING) << "stop already";
    }
  }

  bool isRunning() { return server_ != nullptr; }

private:
  std::string server_address_;
  std::shared_ptr<grpc::ServerBuilder> builder_;
  std::unique_ptr<grpc::Server> server_;
};

template <class StubType>
class GrpcUtilsClientS
{
public:
  explicit GrpcUtilsClientS(const std::string &target)
  {
    grpc::ChannelArguments channel_args;
    channel_args.SetInt(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 2000);
    channel_ = grpc::CreateCustomChannel(
        target, grpc::InsecureChannelCredentials(), channel_args);
    stub_ = std::unique_ptr<StubType>(new StubType(channel_));
  }

  template <class RequestType, class ReplyType>
  grpc::Status CallRpc(grpc::Status (StubType::*rpcMethod)(grpc::ClientContext *,
                                                           const RequestType &,
                                                           ReplyType *),
                       const RequestType &request, ReplyType &reply,
                       int timeout_ms = 0)
  {
    grpc::ClientContext context;
    if (timeout_ms > 0)
    {
      auto deadline = std::chrono::system_clock::now() +
                      std::chrono::milliseconds(timeout_ms);
      context.set_deadline(deadline);
    }
    return (stub_.get()->*rpcMethod)(&context, request, &reply);
  }

private:
  std::shared_ptr<grpc::Channel> channel_;
  std::unique_ptr<StubType> stub_;
};

template <class... StubTypes>
class GrpcUtilsClient
{
public:
  explicit GrpcUtilsClient(const std::string &target)
      : channel_(
            grpc::CreateChannel(target, grpc::InsecureChannelCredentials())),
        stubs_(std::unique_ptr<StubTypes>(new StubTypes(channel_))...) {}

  template <class StubType, class RequestType, class ReplyType>
  grpc::Status CallRpc(grpc::Status (StubType::*rpcMethod)(grpc::ClientContext *,
                                                           const RequestType &,
                                                           ReplyType *),
                       const RequestType &request, ReplyType &reply)
  {
    grpc::ClientContext context;
    return (GetStub<StubType>()->*rpcMethod)(&context, request, &reply);
  }

private:
  std::shared_ptr<grpc::Channel> channel_;
  std::tuple<std::unique_ptr<StubTypes>...> stubs_;

  template <typename StubType>
  StubType *GetStub()
  {
    // static_assert((std::disjunction_v<std::is_same<StubType, StubTypes>...>),
    //               "Requested StubType is not one of the registered stubs.");
    return std::get<std::unique_ptr<StubType>>(stubs_).get();
  }
};

// class GrpcClientReflection {
//  public:
//   struct ReflInfo {
//     struct ServerInfo {
//       struct MethodInfo {
//         const google::protobuf::Descriptor* req_desc = nullptr;
//         const google::protobuf::Descriptor* res_desc = nullptr;
//         const google::protobuf::MethodDescriptor* method_desc = nullptr;
//       };
//       std::map<std::string, MethodInfo> methods;
//       const google::protobuf::ServiceDescriptor* service_desc = nullptr;
//     };
//     std::map<std::string, ServerInfo> servers;
//     std::shared_ptr<google::protobuf::DescriptorPool> desc_pool;
//     google::protobuf::DynamicMessageFactory message_factory;
//   };

//   GrpcClientReflection(std::string server_url);
//   ~GrpcClientReflection();

//   bool listServices(std::vector<std::string>& _servers);

//   std::shared_ptr<google::protobuf::Message> createRequest(
//       std::string service_name, std::string method_name);
//   std::shared_ptr<google::protobuf::Message> grpcCall(
//       std::string service_name, std::string method_name,
//       const google::protobuf::Message& req_msg);

//  private:
//   void init();
//   bool updateReflInfo(const std::vector<std::string>& servers,
//                       ReflInfo& refl_info);
//   bool fetchProtoInfo(const std::string& service_name,
//                       google::protobuf::DescriptorPool& descriptor_pool);
//   void fetchProtoFile(const std::string& proto_name,
//                       google::protobuf::DescriptorPool& descriptor_pool,
//                       std::unordered_set<std::string>& loaded_files);
//   bool checkConnect(int timeout_ms = 1000);
//   void print(const ReflInfo &refl_info);
//   const google::protobuf::MethodDescriptor* getMethodDesc(
//       std::string service_name, std::string method_name);
//   // bool is_init_ = false;
//   // std::map<std::string, ServerReflInfo> servers_info_;
//   std::string server_url_;
//   ReflInfo refl_info_;
//   // std::unique_ptr<grpc::reflection::v1alpha::ServerReflection::Stub> stub_;
//   std::shared_ptr<grpc::Channel> channel_;
// };

// class GrpcClientReflectionEx {
//  public:
//   GrpcClientReflectionEx(std::string server_url);
//   ~GrpcClientReflectionEx();

//   bool checkConnect();

//   bool listServices(std::vector<std::string>& servers);

//   bool createReqRes(std::string service_name, std::string method_name,
//                     std::shared_ptr<google::protobuf::Message>& req_msg,
//                     std::shared_ptr<google::protobuf::Message>& res_msg);

//   bool grpcCall(std::string service_name, std::string method_name,
//                 const google::protobuf::Message& req_msg,
//                 google::protobuf::Message& res_msg);

//   google::protobuf::Message* createReflMsg(const std::string& meg_name);

//   google::protobuf::Message* mutableReflSubMsg(google::protobuf::Message& msg,
//                                                const std::string& field_name);

//  private:
//   void reset();
//   void tryConnect();
//   bool waitForConnect(int timeout_ms);
//   void monitorLoop();
//   std::string server_url_;
//   std::shared_ptr<grpc::Channel> channel_;
//   std::shared_ptr<grpc::ProtoReflectionDescriptorDatabase> reflection_db_;
//   std::shared_ptr<google::protobuf::DescriptorPool> descriptor_pool_;
//   std::shared_ptr<google::protobuf::DynamicMessageFactory> message_factory_;
//   std::mutex mtx_;
//   bool is_running_ = false;
//   std::shared_ptr<std::thread> monitor_thread_;
// };

static void initializeMessage(google::protobuf::Message &message)
{
  const google::protobuf::Descriptor *descriptor = message.GetDescriptor();
  const google::protobuf::Reflection *reflection = message.GetReflection();

  for (int i = 0; i < descriptor->field_count(); i++)
  {
    const google::protobuf::FieldDescriptor *field = descriptor->field(i);

    if (field->is_repeated())
    {
      // 忽略 repeated 字段，不强行填充默认值
      continue;
    }

    switch (field->type())
    {
    case google::protobuf::FieldDescriptor::TYPE_MESSAGE:
    {
      // 递归初始化 message 类型字段
      google::protobuf::Message *sub_message =
          reflection->MutableMessage(&message, field);
      initializeMessage(*sub_message);
      break;
    }
    case google::protobuf::FieldDescriptor::TYPE_STRING:
      reflection->SetString(&message, field, ""); // 赋值默认空字符串
      break;
    case google::protobuf::FieldDescriptor::TYPE_INT32:
    case google::protobuf::FieldDescriptor::TYPE_INT64:
    case google::protobuf::FieldDescriptor::TYPE_UINT32:
    case google::protobuf::FieldDescriptor::TYPE_UINT64:
      reflection->SetInt32(&message, field, 0); // 赋值默认 0
      break;
    case google::protobuf::FieldDescriptor::TYPE_BOOL:
      reflection->SetBool(&message, field, false); // 赋值默认 false
      break;
    case google::protobuf::FieldDescriptor::TYPE_FLOAT:
    case google::protobuf::FieldDescriptor::TYPE_DOUBLE:
      reflection->SetDouble(&message, field, 0.0); // 赋值默认 0.0
      break;
    default:
      break;
    }
  }
}

static std::string protoMessageToJson(
    const google::protobuf::Message &message)
{
  std::string json_output;
  google::protobuf::util::JsonPrintOptions options;
  options.add_whitespace = true;
  options.always_print_primitive_fields = true;
  options.preserve_proto_field_names = true;
  google::protobuf::util::MessageToJsonString(message, &json_output, options);
  return json_output;
}

[[maybe_unused]] static bool saveJson(
    const std::string &file, const google::protobuf::Message &message)
{
  std::string json_output = protoMessageToJson(message);
  std::ofstream fs(file);
  if (!fs.is_open())
  {
    LOG(ERROR) << "open file failed: " << file;
    return false;
  }
  fs << json_output.c_str();
  fs.close();
  return true;
}

[[maybe_unused]] static bool loadJson(const std::string &file,
                                      google::protobuf::Message &message)
{
  std::ifstream fs(file);
  if (!fs)
  {
    LOG(ERROR) << "open file failed: " << file << std::endl;
    return false;
  }
  std::ostringstream buffer;
  buffer << fs.rdbuf();
  fs.close();
  std::string json = buffer.str();
  google::protobuf::util::JsonParseOptions options;
  options.ignore_unknown_fields = false;
  google::protobuf::util::Status status =
      google::protobuf::util::JsonStringToMessage(json, &message);
  if (!status.ok())
  {
    LOG(ERROR) << "JsonStringToMessage() failed: " << status.ToString()
               << std::endl;
    return false;
  }
  return true;
}

bool getProtoEnumStr(const google::protobuf::Message &msg,
                     const std::string &field_name, std::string &field_value);
bool setProtoEnumStr(google::protobuf::Message &msg,
                     const std::string &field_name,
                     const std::string &field_value);
google::protobuf::Message *mutableSubMessage(google::protobuf::Message &msg,
                                             const std::string &field_name);